﻿/*
Copyright © 2011 François Karman

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

See <license.txt> for the complete license of the software.
*/

using System;
using System.IO;
using System.Runtime.Serialization;
using Simple.Storage.Serializer;

namespace Simple.Storage.Api
{
    /// <summary>
    /// Implements the <see cref="ISerializer"/> contract for the class definition
    /// as part of the default serialization strategy.
    /// </summary>
    /// <remarks>
    /// This serializer breaks the <see cref="ISerializer"/> contract has the default
    /// deserialization (that creates a new instance) is not implemented.
    /// As a result, this implementation should not be used as part of a chain-of-responsibility.
    /// </remarks>
    /// <seealso cref="IInitializer.CreateClassSerializer"/>
    public sealed class ClassSerializer : AbstractMonoTypeSerializer
    {
        /// <summary>
        /// The associated class definition.
        /// </summary>
        private readonly IClassDefinition definition;

        /// <summary>
        /// The serializer(s) used for all the properties of the class instances.
        /// </summary>
        private readonly ISerializer propertySerializer;

        /// <summary>
        /// Initializes a new instance of the ClassSerializer class.
        /// </summary>
        /// <param name="definition">
        /// The associated class definition.
        /// </param>
        /// <param name="propertySerializer">
        /// The serializer(s) used for all the properties of the class instances.
        /// </param>
        public ClassSerializer(IClassDefinition definition, ISerializer propertySerializer)
        {
            if (definition == null)
            {
                throw new ArgumentNullException("definition");
            }
            else if (propertySerializer == null)
            {
                throw new ArgumentNullException("propertySerializer");
            }

            this.definition = definition;
            this.propertySerializer = propertySerializer;
        }

        /// <summary>
        /// Gets the type of elements managed by the serializer.
        /// </summary>
        /// <value>
        /// The type associated with the class definition.
        /// </value>
        protected override Type AssociatedType
        {
            get { return this.definition.Type; }
        }

        /// <summary>
        /// Checks whether the <see cref="ISerializer.Deserialize(BinaryReader, Type, object)"/>*
        /// method is implemented by this serializer.
        /// </summary>
        /// <param name="type">The checked type.</param>
        /// <returns>
        /// The same value than <see cref="ISerializer.CanSerialize(Type)"/>.
        /// </returns>
        protected override bool InternalCanWorkInPlace(Type type)
        {
            return this.CanSerialize(type);
        }

        /// <summary>
        /// Implements the serialization logic for the type associated with this serializer.
        /// </summary>
        /// <param name="writer">
        /// The writer - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="value">
        /// The value to serialize - guaranty to be not <c>null</c> and associated with
        /// the current serializer.
        /// </param>
        protected override void InternalSerialize(BinaryWriter writer, object value)
        {
            foreach (IPropertyDefinition property in this.definition.Properties)
            {
                this.propertySerializer.Serialize(writer, property.Type, property.GetValue(value));
            }
        }

        /// <summary>
        /// Implements the in-place deserialization logic.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="type">
        /// The reference type - guaranty to be not <c>null</c>.
        /// </param>
        /// <param name="instance">
        /// The container for the deserialized object - guaranty to be not <c>null</c>.
        /// </param>
        protected override void InternalDeserialize(BinaryReader reader, Type type, object instance)
        {
            if (type != this.AssociatedType)
            {
                string message = string.Format(
                    Messages.Culture,
                    Messages.SerializerWrongType,
                    this.AssociatedType.FullName,
                    type.FullName);
                throw new SerializationException(message);
            }

            foreach (IPropertyDefinition property in this.definition.Properties)
            {
                if (property.RequireInPlace)
                {
                    object value = property.GetValue(instance);
                    this.propertySerializer.Deserialize(reader, property.Type, value);
                }
                else
                {
                    object value = this.propertySerializer.Deserialize(reader, property.Type);
                    property.SetValue(instance, value);
                }
            }
        }

        /// <summary>
        /// Implements the deserialization logic for the type associated with this serializer
        /// - always raises a <see cref="NotSupportedException"/> exception.
        /// </summary>
        /// <param name="reader">
        /// The reader - guaranty to be not <c>null</c>.
        /// </param>
        /// <returns>Always raises a <see cref="NotSupportedException"/> exception.</returns>
        /// <exception cref="NotSupportedException">
        /// This serializer doesn't support normal deserialization process.
        /// </exception>
        protected override object InternalDeserialize(BinaryReader reader)
        {
            throw new NotSupportedException();
        }
    }
}
